#include "observable.h"
#include "observed_ptr.h"
#include "thread_pool.h"
#include "gtest/gtest.h"
#include <functional>
#include <iostream>

using crude::IObservable;
using crude::observed_ptr;
using crude::subject_ptr;

class ObservableTest : public testing::Test {};

namespace {
constexpr int PERSON_SIZE = 5;
const char *Names[PERSON_SIZE] = {"Tom", "Bob", "Jim", "Lily", "Jerry"};
class Person {
  static int count_;
  int age_;
  std::string name_;

public:
  static int Count() { return count_; }
  explicit Person(int age = 0, const char *name = "") : age_(age), name_(name) {
    count_++;
  }
  ~Person() { std::cout << __FUNCTION__ << --count_ << std::endl; }

  std::string GetName() { return name_.c_str(); }

  void SetName(const char *name) { name_ = name; }

  void SetAge(int age) { age_ = age; }

  int GetAge() { return age_; }
};
int Person::count_ = 0;

class Student {
  static int count_;
  int age_;
  std::string name_;
  ~Student() { std::cout << __FUNCTION__ << --count_ << std::endl; }
  std::unique_ptr<int> a;

public:
  static int Count() { return count_; }
  Student(int age = 0, const char *name = "") : age_(age), name_(name) {
    count_++;
  }

  void Release() {
    std::cout << __FUNCTION__;
    delete this;
  }

  std::string GetName() { return name_.c_str(); }

  int GetAge() { return age_; }

  char32_t t;
};
int Student::count_ = 0;
} // namespace

class PseudoObservable : public IObservable<PseudoObservable> {
public:
  PseudoObservable() {}
  int SomeMethod() { return 42; }
  size_t ActiveObservedPtrs() const { return GetObserversCount(); }
};

TEST_F(ObservableTest, Null) {
  PseudoObservable::Observer ptr;
  EXPECT_EQ(nullptr, ptr.Get());
}

TEST_F(ObservableTest, LivesLonger) {
  PseudoObservable *pObs = new PseudoObservable;
  PseudoObservable *pObs2 = pObs;
  PseudoObservable::Observer ptr(pObs);
  EXPECT_NE(nullptr, ptr.Get());
  EXPECT_EQ(1u, pObs->ActiveObservedPtrs());
  delete pObs;
  EXPECT_EQ(nullptr, ptr.Get());
  EXPECT_NE(nullptr, pObs2);
}

TEST_F(ObservableTest, LivesShorter) {
  PseudoObservable obs;
  {
    PseudoObservable::Observer ptr(&obs);
    EXPECT_NE(nullptr, ptr.Get());
    EXPECT_EQ(1u, obs.ActiveObservedPtrs());
  }
  EXPECT_EQ(0u, obs.ActiveObservedPtrs());
}

TEST_F(ObservableTest, CopyConstruct) {
  PseudoObservable obs;
  {
    PseudoObservable::Observer ptr(&obs);
    EXPECT_NE(nullptr, ptr.Get());
    EXPECT_EQ(1u, obs.ActiveObservedPtrs());
    {
      PseudoObservable::Observer ptr2(ptr);
      EXPECT_NE(nullptr, ptr2.Get());
      EXPECT_EQ(2u, obs.ActiveObservedPtrs());
    }
    EXPECT_EQ(1u, obs.ActiveObservedPtrs());
  }
  EXPECT_EQ(0u, obs.ActiveObservedPtrs());
}

TEST_F(ObservableTest, CopyAssign) {
  PseudoObservable obs;
  {
    PseudoObservable::Observer ptr(&obs);
    EXPECT_NE(nullptr, ptr.Get());
    EXPECT_EQ(1u, obs.ActiveObservedPtrs());
    {
      PseudoObservable::Observer ptr2;
      ptr2 = ptr;
      EXPECT_NE(nullptr, ptr2.Get());
      EXPECT_EQ(2u, obs.ActiveObservedPtrs());
    }
    EXPECT_EQ(1u, obs.ActiveObservedPtrs());
  }
  EXPECT_EQ(0u, obs.ActiveObservedPtrs());
}

TEST_F(ObservableTest, Vector) {
  PseudoObservable obs;
  {
    std::vector<PseudoObservable::Observer> vec1;
    std::vector<PseudoObservable::Observer> vec2;
    vec1.emplace_back(&obs);
    vec1.emplace_back(&obs);
    EXPECT_NE(nullptr, vec1[0].Get());
    EXPECT_NE(nullptr, vec1[1].Get());
    EXPECT_EQ(2u, obs.ActiveObservedPtrs());
    vec2 = vec1;
    EXPECT_NE(nullptr, vec2[0].Get());
    EXPECT_NE(nullptr, vec2[1].Get());
    EXPECT_EQ(4u, obs.ActiveObservedPtrs());
    vec1.clear();
    EXPECT_EQ(2u, obs.ActiveObservedPtrs());
    vec2.resize(10000);
    EXPECT_EQ(2u, obs.ActiveObservedPtrs());
    vec2.resize(0);
    EXPECT_EQ(0u, obs.ActiveObservedPtrs());
  }
  EXPECT_EQ(0u, obs.ActiveObservedPtrs());
}

TEST_F(ObservableTest, VectorAutoClear) {
  std::vector<PseudoObservable::Observer> vec1;
  {
    PseudoObservable obs;
    vec1.emplace_back(&obs);
    vec1.emplace_back(&obs);
    EXPECT_NE(nullptr, vec1[0].Get());
    EXPECT_NE(nullptr, vec1[1].Get());
    EXPECT_EQ(2u, obs.ActiveObservedPtrs());
  }
  EXPECT_EQ(nullptr, vec1[0].Get());
  EXPECT_EQ(nullptr, vec1[1].Get());
}

TEST_F(ObservableTest, ResetNull) {
  PseudoObservable obs;
  PseudoObservable::Observer ptr(&obs);
  EXPECT_EQ(1u, obs.ActiveObservedPtrs());
  ptr.Reset();
  EXPECT_EQ(0u, obs.ActiveObservedPtrs());
}

TEST_F(ObservableTest, Reset) {
  PseudoObservable obs1;
  PseudoObservable obs2;
  PseudoObservable::Observer ptr(&obs1);
  EXPECT_EQ(1u, obs1.ActiveObservedPtrs());
  EXPECT_EQ(0u, obs2.ActiveObservedPtrs());
  ptr.Reset(&obs2);
  EXPECT_EQ(0u, obs1.ActiveObservedPtrs());
  EXPECT_EQ(1u, obs2.ActiveObservedPtrs());
}

TEST_F(ObservableTest, Equals) {
  PseudoObservable obj1;
  PseudoObservable obj2;
  PseudoObservable::Observer null_ptr1;
  PseudoObservable::Observer obj1_ptr1(&obj1);
  PseudoObservable::Observer obj2_ptr1(&obj2);
  {
    PseudoObservable::Observer null_ptr2;
    EXPECT_TRUE(null_ptr1 == null_ptr2);

    PseudoObservable::Observer obj1_ptr2(&obj1);
    EXPECT_TRUE(obj1_ptr1 == obj1_ptr2);

    PseudoObservable::Observer obj2_ptr2(&obj2);
    EXPECT_TRUE(obj2_ptr1 == obj2_ptr2);
  }
  EXPECT_FALSE(null_ptr1 == obj1_ptr1);
  EXPECT_FALSE(null_ptr1 == obj2_ptr1);
  EXPECT_FALSE(obj1_ptr1 == obj2_ptr1);
}

TEST_F(ObservableTest, NotEquals) {
  PseudoObservable obj1;
  PseudoObservable obj2;
  PseudoObservable::Observer null_ptr1;
  PseudoObservable::Observer obj1_ptr1(&obj1);
  PseudoObservable::Observer obj2_ptr1(&obj2);
  {
    PseudoObservable::Observer null_ptr2;
    PseudoObservable::Observer obj1_ptr2(&obj1);
    PseudoObservable::Observer obj2_ptr2(&obj2);
    EXPECT_FALSE(null_ptr1 != null_ptr2);
    EXPECT_FALSE(obj1_ptr1 != obj1_ptr2);
    EXPECT_FALSE(obj2_ptr1 != obj2_ptr2);
  }
  EXPECT_TRUE(null_ptr1 != obj1_ptr1);
  EXPECT_TRUE(null_ptr1 != obj2_ptr1);
  EXPECT_TRUE(obj1_ptr1 != obj2_ptr1);
}

TEST_F(ObservableTest, Bool) {
  PseudoObservable obj1;
  PseudoObservable::Observer null_ptr;
  PseudoObservable::Observer obj1_ptr(&obj1);
  bool null_bool = !!null_ptr;
  bool obj1_bool = !!obj1_ptr;
  EXPECT_FALSE(null_bool);
  EXPECT_TRUE(obj1_bool);
}

TEST_F(ObservableTest, Thread) {
  PseudoObservable obj1;
  auto AddObserver = [](PseudoObservable *obj) {
    std::vector<PseudoObservable::Observer> obrs;
    for (int i = 0; i < 10; i++) {
      obrs.push_back(PseudoObservable::Observer(obj));
    }
  };

  crude::ThreadPool pool(4);
  for (int i = 0; i < 10; i++) {
    pool.async(AddObserver, &obj1);
  }

  pool.wait();

  ASSERT_EQ(0, obj1.GetObserversCount());
}

TEST_F(ObservableTest, Thread_subject_ptr) {
  auto AddObserver = [](const subject_ptr<PseudoObservable> &obj) {
    std::vector<observed_ptr<PseudoObservable>> obrs;
    for (int i = 0; i < 10; i++) {
      obrs.push_back(observed_ptr<PseudoObservable>(obj));
    }
  };

  auto obj1 = crude::make_subject<PseudoObservable>();

  crude::ThreadPool pool(4);
  for (int i = 0; i < 10; i++) {
    pool.async(AddObserver, std::ref(obj1));
  }

  pool.wait();

  ASSERT_EQ(0, obj1.get_observer_count());
}

template <class Func, class... Args>
void process(Func &&func, Args &&... args) {
  auto start = std::chrono::system_clock::now();
  std::forward<Func>(func)(std::forward<Args>(args)...);
  std::cout << "count time : "
            << std::chrono::duration<double>(std::chrono::system_clock::now() -
                                             start)
                   .count()
            << "\n";
}

TEST_F(ObservableTest, TimeEfficiency) {
  auto AddRawPointer = [](PseudoObservable *obj) {
    std::vector<PseudoObservable *> obrs;
    for (int i = 0; i < 10000; i++) {
      obrs.push_back(obj);
    }
  };

  auto AddObserver = [](PseudoObservable *obj) {
    std::vector<PseudoObservable::Observer> obrs;
    for (int i = 0; i < 10000; i++) {
      obrs.push_back(PseudoObservable::Observer(obj));
    }
  };

  auto AddObserved_Ptr = [](subject_ptr<PseudoObservable> &&obj) {
    std::vector<observed_ptr<PseudoObservable>> obrs;
    for (int i = 0; i < 10000; i++) {
      obrs.push_back(observed_ptr<PseudoObservable>(obj));
    }
    std::cout << obj.get_observer_count() << std::endl;
  };

  process(AddRawPointer, &PseudoObservable());
  process(AddObserver, &PseudoObservable());
  process(AddObserved_Ptr, crude::make_subject<PseudoObservable>());
}
